package com.guguguweb.rpc.net.client.proxy;

import com.guguguweb.rpc.entity.RpcRequest;
import com.guguguweb.rpc.entity.RpcResponse;
import com.guguguweb.rpc.enumeration.ResponseCode;
import com.guguguweb.rpc.enumeration.RpcError;
import com.guguguweb.rpc.exception.RpcException;
import com.guguguweb.rpc.net.client.GuRpcClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;



public class GuRpcClientProxy  implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(GuRpcClientProxy.class);


    private static final String INTERFACE_NAME = "interfaceName";
    private final GuRpcClient client;


    private final Map<Class ,Object> map = new ConcurrentHashMap<>();


    public  GuRpcClientProxy(GuRpcClient client) {
        this.client = client;
    }



    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz){
        //创建代理对象
        Object o1 = map.get(clazz);
        if(o1 != null){
            return (T)o1;
        }
        synchronized(this){
            o1 = map.get(clazz);
            if(o1 != null){
                return (T)o1;
            }
            Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this);
            log.info("客户端 接口 {} ,代理类创建完毕",clazz.getName());
            map.put(clazz,o);
            return (T)o;
        }
    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(log.isDebugEnabled()){
            log.debug("调用方法：{}  {}", method.getDeclaringClass().getName(), method.getName());
        }
        String methodClassName = method.getDeclaringClass().getName();
        String methodName = method.getName();
        RpcRequest rpcRequest = new RpcRequest(UUID.randomUUID().toString(), methodClassName,
                methodName, args, method.getParameterTypes(), false);
        RpcResponse rpcResponse = null;
        try {

            if(log.isDebugEnabled()){
                log.debug("发送请求： {}",rpcRequest);
            }
            CompletableFuture<RpcResponse> future = client.sendRequest(rpcRequest);
            rpcResponse = future.get(client.getMaxWaitTime(),TimeUnit.MILLISECONDS);//阻塞超时等待，
        }
        catch (TimeoutException e){
            log.error("rpc方法调用请求超时，方法： {} , 接口 ： {} ",methodName,methodClassName,e);
            client.getRequestsMap().remove(rpcRequest.getRequestId());
            throw e;
        }
        catch (Exception e){
            log.error("rpc方法调用请求发送失败，方法： {} , 接口 ： {} ",methodName,methodClassName,e);
            throw e;
        }
        check(rpcRequest,rpcResponse);
        return rpcResponse.getData();
    }

    private static void check(RpcRequest rpcRequest, RpcResponse rpcResponse){
        if(rpcResponse == null) {
            log.error("调用服务失败，serviceName:{}", rpcRequest.getInterfaceName());
            throw new RpcException(RpcError.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
        if(rpcResponse.getStatusCode()!= 200){
            throw new RpcException(RpcError.RESPONSE_NOT_MATCH, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
        //响应与请求的请求号不同
        if(!rpcRequest.getRequestId().equals(rpcResponse.getRequestId())) {
            throw new RpcException(RpcError.RESPONSE_NOT_MATCH, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
        //调用失败
        if(rpcResponse.getStatusCode() == null || !rpcResponse.getStatusCode().equals(ResponseCode.SUCCESS.getCode())){
            log.error("调用服务失败，serviceName:{}，RpcResponse:{}", rpcRequest.getInterfaceName(), rpcResponse);
            throw new RpcException(RpcError.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
    }
}
